package com.sunnystudio.mydiary.service;

import java.io.File;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URL;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;

import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Environment;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.GpsStatus.NmeaListener;
import android.os.Bundle;

import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;

import com.airbiquity.android.R;
import com.airbiquity.android.fleet.ics.IcsDirector;
import com.airbiquity.android.fleet.ics.IcsDirectorInterface;
import com.airbiquity.android.fleet.icsobjs.IcsConstants;
import com.airbiquity.android.sfsp.UpdateManager;
import com.airbiquity.android.sfsp.database.DBConstants;
import com.airbiquity.android.sfsp.database.DBInstance;
import com.airbiquity.android.sfsp.database.ObjAlert;
import com.airbiquity.android.sfsp.database.ObjConfig;
import com.airbiquity.android.sfsp.database.ObjMessage;
import com.airbiquity.android.sfsp.database.ObjShiftAbstract;
import com.airbiquity.android.sfsp.database.ObjTargetWeight;
import com.airbiquity.aqlog.AqLog;
import com.airbiquity.util.AqConsts;
import com.airbiquity.util.AqFileUtils;
import com.airbiquity.util.DriverReport;
import com.airbiquity.util.SystemManager;

public class WebviewService extends Service {
	private static final String TAG = "WebviewService";
	
	private final IBinder binder = new LocalBinder();  
	private DBInstance dbInstance;
	private Handler notifyHandler;
	private int mNotificationId = 1321437580;
	private NotificationManager mNotificationManager;
	
	private boolean isPinValid = false;
	private ObjConfig config; // Current user configuration
	private boolean isVehicleMoving = false;
	private String shiftId = "";
	
	/**
	 * Use for switch to navigator when vehicle in motion.
	 * If the webview is wake from backend for unread message, set as true.
	 * When switch to navigator, set as false.
	 */
	private boolean isWakeFromBack = false;

        // CoPilot Truck Europe
	private final static String NAVAPPPKG = "com.alk.copilot.market.eu.major.truck";
	private final static String NAVAPPCLASS = "com.alk.copilot.Copilot";
    
	private LocationManager locationManager = null;
	private MyLocationListener myLocListener = new MyLocationListener();
	private MyNmeaListener myNmeaListener = new MyNmeaListener();
	
	// CoPilot Live Premium Europe 
        // private final static String NAVAPPPKG = "com.alk.copilot.eumarket.premiumeupan";
	// private final static String NAVAPPCLASS = "com.alk.copilot.CopilotActivity";
	
	// CoPilot Live premium UK+IRE
//	private final static String NAVAPPPKG = "com.alk.copilot.eumarket.premiumeumaj";
//	private final static String NAVAPPCLASS = "com.alk.copilot.CopilotActivity";

//	private final static String NAVAPPPKG = "com.android.settings";
//	private final static String NAVAPPCLASS = "com.android.settings.Settings";

	public class LocalBinder extends Binder {  
    	public WebviewService getService() {  
            return WebviewService.this;  
        }  
    } 
    
	double gpsLatitude=0;
	double gpsLongitude=0;
	
	private synchronized void setLatitude( double lat )
	{
		gpsLatitude=lat;
	}

	private synchronized void setLongitude( double lon )
	{
		gpsLongitude=lon;
	}
	
	private synchronized double getLatitude()
	{
		return gpsLatitude;
	}
	
	private synchronized double getLongitude()
	{
		return gpsLongitude;
	}
	
	private class MyNmeaListener implements NmeaListener
	{
		@Override
		public void onNmeaReceived(long timestamp, String nmea) {
			if( ( null != nmea ) && ( nmea.startsWith("$GPGGA" ) || nmea.startsWith( "$GPRMC") ) )
			{
				AqLog.getInstance().debug("AQ NMEA: " + nmea );
			}
		}
	}
	
	private class MyLocationListener implements LocationListener
	{

		@Override
		public void onLocationChanged(Location location) {
			Log.d("WVS LOCATION", "lat,lon,alt,speed,heading,hasAccuracy,accuracy:" + location.getLatitude() + "," + location.getLongitude() + "," + location.getAltitude() + "," + location.getSpeed() + "," + location.getBearing() + "," + location.hasAccuracy() + "," + location.getAccuracy() );
			setLatitude( location.getLatitude() );
			setLongitude( location.getLongitude() );
		}

		@Override
		public void onProviderDisabled(String provider) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void onProviderEnabled(String provider) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void onStatusChanged(String provider, int status, Bundle extras) {
			// TODO Auto-generated method stub
			
		}
		
	}
	
    /**
     * Handler the message from IcsDirector
     * 
     */
	private Handler handler = new Handler() {
		@Override
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
			
			Log.d(TAG, "----webservice----handle message: "+ msg.obj.toString());
			
			if (notifyHandler != null) {
				switch (msg.what) {
					case IcsDirectorInterface.MSG_HANDLE_OBU_STATUS:
						showObuStatus(msg.what, msg.obj.toString());
						break;
					case IcsDirectorInterface.MSG_RESET:
						switchDriver(msg.what, msg.obj.toString());
						break;
					case IcsDirectorInterface.MSG_HANDLE_TEXT_MESSAGE:
						showTextMsg(msg.what, msg.obj.toString());
						break;
					case IcsDirectorInterface.MSG_HANDLE_ALERT:
						showAlert(msg.what, msg.obj.toString());
						break;
					case IcsDirectorInterface.MSG_DRIVER_REPORT_READY:
						showNormalNotification(msg.what, msg.obj.toString());
						break;
					case IcsDirectorInterface.MSG_DRIVER_REPORT_READY_AT_END_OF_SHIFT:
						showNormalNotification(msg.what, msg.obj.toString());
						break;
					case IcsDirectorInterface.MSG_ENGINE_OFF:
						showNormalNotification(msg.what, msg.obj.toString());
						break;
	
					case IcsDirectorInterface.MSG_UPDATE_ICS_APK:
						updateApk(msg.obj.toString());
						break;
						
					case IcsDirectorInterface.MSG_DISPLAY_ICS_SW_VERSION:
						AqLog.getInstance().info("ICS_VERSION: " + getIcsVersionString() );
						break;
						
					default:
						break;
				}
			}
		}
	};
    
	/**
	 * Get user configuration by driver Id from database
	 * @param String driverId
	 * @return ObjConfig
	 */
	private ObjConfig getConfigFromDB(String driverId) {
		ObjConfig cfg = dbInstance.getConfig(driverId);
		return cfg;
	}
	
	/**
	 * Make the webview activity in foreground
	 */
	private void wakeupWebview() {
		this.isWakeFromBack = true; 
		
		Intent intent = new Intent();
        intent.setAction(DaemonService.ACTION_DAEMON);
        intent.putExtra("cmd", DaemonService.CMD_APP_RESTART);
        sendBroadcast(intent);
	}
	
	/**
	 * Show OBU status
	 * @param int what
	 * @param String msg
	 */
	private void showObuStatus(int what, String msg) {
		boolean lastStatus = isVehicleMoving;
		
		float speed = 0.0f;
		try {
			JSONObject json = new JSONObject(msg);
			speed = Float.valueOf(json.optString(IcsConstants.KEY_VEHICLE_SPEED));
			shiftId = json.optString(IcsConstants.KEY_SHIFT_ID);
		} catch (JSONException e) {
			Log.e(TAG, "Json error: ", e);
		}
		
		if (speed > 0) {
			isVehicleMoving = true;
		} else {
			isVehicleMoving = false;
		}
		
		// if status does not change, do nothing
		if (isVehicleMoving != lastStatus) {
			String result = "";
			try {
				JSONObject statusJson = new JSONObject();
				statusJson.put("is_vehicle_moving", isVehicleMoving);
				result = statusJson.toString();
			}  catch (JSONException e) {
				Log.e(TAG, "Json error: ", e);
			}
			
			if (!result.isEmpty()) {
				showMessage(what, result, false);
			}
			
			Log.v(TAG, "--sam-----------test");
			Log.v(TAG, "whether to open navigation "+getIsVehicleMoving()+"   "+getIsNavigationActive()+"   ");
			if (getIsVehicleMoving() && getIsNavigationActive()) {
	    		openNavigation("");
			}
		}
	}
	
	/**
	 * Switch current user when receive reset event
	 * @param String driverId
	 * @param int driverType
	 */
	private void switchDriver(int what, String msg) {
		String driverId = DBConstants.UNKNOWN_DRIVERID;
		int driverType = IcsConstants.DRIVER_ID_TYPE_UNKNOWN;
		try {
			JSONObject json = new JSONObject(msg);
			driverId = json.optString(IcsConstants.KEY_DRIVER_ID);
			driverType = json.optInt(IcsConstants.KEY_DRIVER_ID_TYPE);
		} catch (JSONException e) {
			Log.e(TAG, "Json error: ", e);
		}
		
		if (driverType == IcsConstants.DRIVER_ID_TYPE_UNKNOWN && ( driverId == null || ("").equals(driverId))) {
			driverId = DBConstants.UNKNOWN_DRIVERID;
			driverType = IcsConstants.DRIVER_ID_TYPE_UNKNOWN;
		}
		
		setDriver(driverId, driverType);
		
		isPinValid = true;
		
		String setting = getSetting("{\"user_id\":\""+ driverId +"\"}");
		
		if (setting != null) {
			showNormalNotification(what, setting);
		}
	}
	
	/**
	 * Set current driver
	 * @param driverId: String
	 * @param driverType: int
	 */
	private void setDriver(String driverId, int driverType) {
		// if driver is same with current, do nothing
		if (driverId.equals(this.config.getDriver_id())) return;
		
		ObjConfig cfg = getConfigFromDB(driverId);
		if (cfg.getId() > 0) {
			if (cfg.getDriver_type() != driverType) {
				cfg.setDriver_type(driverType);
				dbInstance.updateConfig(cfg);
			}
			
			this.config = cfg;
		} else {
			cfg.setDriver_id(driverId);
			cfg.setDriver_type(driverType);
			long id = dbInstance.addConfig(cfg);
			if (id > 0) this.config = cfg;
		}
	}
	
	/**
	 * Show text message
	 * @param int what
	 * @param String msg
	 */
	private void showTextMsg(int what, String msg) {
		int priority = 0;
		String msgTo = "";
		int msgToType = 0;
		try {
			JSONObject json = new JSONObject(msg);
			priority = json.optInt(IcsConstants.KEY_PRIORITY);
			msgTo = json.optString(IcsConstants.KEY_MSG_TO);
			msgToType = json.optInt(IcsConstants.KEY_MSG_TO_TYPE);
		} catch (Exception e) {
			Log.e(TAG, "Json error: ", e);
		}
		
		// If driver set "display high msg while vehicle is in motion" to off,
		// don't show this message to driver
//		if (this.isVehicleMoving
//				&& (this.config.getShow_high_msg() == DBConstants.OPTION_OFF || priority <= 5))
//			return;

		// If driver login with valid PIN and accept EULA, 
		// send sound notification for messages to vehicle and current driver
		if (isPinValid && (this.config.getIs_eula_accepted() == DBConstants.OPTION_ON) 
				&& (msgToType == IcsConstants.ID_TYPE_VEHICLE
				|| (!msgTo.isEmpty() && config.getDriver_id().equals(msgTo)))) {
			boolean isFocus = true;
			
			if (this.isVehicleMoving
					&& (this.config.getShow_high_msg() == DBConstants.OPTION_OFF || priority <= 5)) {
				isFocus = false;
			}
			
			showMessage(what, msg, isFocus);
			
			// If driver set "display high msg while vehicle is in motion" to off,
			if (!isFocus) return;
			
			// If previous notification exists, cancel first
			if (mNotificationManager != null) mNotificationManager.cancel(mNotificationId);
						
			if (config.getAudio() == DBConstants.OPTION_ON) { // if audio setting is on
				Uri sound;
				
				if (priority > 5) {
					//notification.flags = Notification.FLAG_INSISTENT;
					//sound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
					sound = Uri.parse("android.resource://" +getPackageName()+ "/raw/" + R.raw.alarm);
				} else {
					//sound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
					sound = Uri.parse("android.resource://" +getPackageName()+ "/raw/" + R.raw.notification);
				}
				showSoundNotification(sound);
			}
		}
	}
	
	/**
	 * Show alert message
	 * @param int what
	 * @param String msg
	 */
	private void showAlert(int what, String msg) {
		String alertShiftId = "";
		try {
			JSONObject json = new JSONObject(msg);
			alertShiftId = json.optString(IcsConstants.KEY_SHIFT_ID);
		} catch (Exception e) {
			Log.e(TAG, "Json error: ", e);
		}
		
		// Just show alert to user with same shift ID
		if (!this.shiftId.equals(alertShiftId)) return;
		
		if (isPinValid && (this.config.getIs_eula_accepted() == DBConstants.OPTION_ON) ) {
			showMessage(what, msg, true);
			
			// If driver set "display high msg while vehicle is in motion" to off,
			if (this.isVehicleMoving
					&& (this.config.getShow_high_alert() == DBConstants.OPTION_OFF))
				return;
			
			// If previous notification exists, cancel first
			if (mNotificationManager != null) mNotificationManager.cancel(mNotificationId);
						
			if (config.getAudio() == DBConstants.OPTION_ON) { // if audio setting is on
				//Uri sound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
				Uri sound = Uri.parse("android.resource://" +getPackageName()+ "/raw/" + R.raw.alarm);
				showSoundNotification(sound);
			}
		}
	}
	
	
	private void showNormalNotification(int what,String msg){
		boolean isFocus = true;
		if(getIsVehicleMoving()){
			isFocus = false;
		}
		showMessage(what, msg, isFocus);
		
	}
	
	/**
	 * Show message to HMI
	 * 
	 * @param int what: Value to assign to the returned Message.what field
	 * @param String msg: Message body
	 * @param boolean isFocus: If true, wakeup webview
	 */
	private void showMessage(int what, String msg, boolean isFocus) {		
		boolean shouldBackNav = isNavigationActive(1);
		
		Message message = notifyHandler.obtainMessage(what);
		message.getData().putString("json", msg);
		message.getData().putBoolean("navigationSelected", shouldBackNav);
		notifyHandler.sendMessage(message);
				
		if (isFocus) wakeupWebview();
	}
	
	/**
	 * Show sound notification
	 * @param Uri uri: Resource uri of audio file
	 */
	private void showSoundNotification(Uri uri) {
            // Set the volume of notification
            AudioManager mAudioManager = (AudioManager) getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 0);
            mAudioManager.setStreamVolume(AudioManager.STREAM_NOTIFICATION, mAudioManager.getStreamMaxVolume(AudioManager.STREAM_NOTIFICATION), 0);
            
            mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            Notification notification = new Notification();
            notification.sound = uri;
		
            mNotificationManager.notify(mNotificationId, notification);
	}
	
	/**
	 * Get if current activity is navigation application
	 * 
	 * @param maxNum int, max num to find in running tasks
	 * @return true|false
	 */
	private boolean isNavigationActive(int maxNum) {

		if( this.getNavmiiOpenCount() > 0 )
		{
			return true;
		}
		else
		{
			boolean isActive = false;

			if (maxNum < 1 || maxNum > 10) maxNum = 10;
			ActivityManager activityManager = (ActivityManager) getApplicationContext()
					.getSystemService(ACTIVITY_SERVICE);

			List<ActivityManager.RunningTaskInfo> runningTasks = activityManager
					.getRunningTasks(maxNum);

			if (runningTasks.isEmpty()) {
				return false;
			}
			Log.v(TAG, "runningTasks.size: "+runningTasks.size());
			if(maxNum >runningTasks.size()){
				Log.v(TAG, "runningTasks.size= sam ==="+runningTasks.size());
				maxNum = runningTasks.size();
			}
			Properties p = SystemManager.getInstance().loadProperties();
			if( null == p ) p = new Properties();
			String className = p.getProperty("nav_app", NAVAPPCLASS);

			for (int i=0; i< maxNum; i++) {
				String currentActivity = runningTasks.get(i).baseActivity.getClassName();
				if (currentActivity.equals(className)) {
					isActive = true;
					break;
				}
			}
			return isActive;
		}
	}
	
    @Override
    public void onCreate() {
    	super.onCreate();
    	
    	dbInstance = DBInstance.getInstance(getApplicationContext());
    	IcsDirector.getInstance().setContext( getApplicationContext() );
    	IcsDirector.getInstance().setWebviewServiceHandler(handler);
    	IcsDirector.getInstance().setDbInstance(dbInstance);
    	
    	this.config = getConfigFromDB(DBConstants.UNKNOWN_DRIVERID);
    	
    	try
    	{
    		locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
    		locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 500, 1, myLocListener );
    		locationManager.addNmeaListener( myNmeaListener );
    	}
    	catch( Exception e )
    	{
    		AqLog.getInstance().error("Unable to register for location updates", e );
    	}
    }
    
	@Override
	public IBinder onBind(Intent arg0) {
		return binder;  
	}
	
	@Override
	public void onDestroy() {    	
		if (this.dbInstance != null) this.dbInstance.close();
		
		this.notifyHandler = null;
		this.dbInstance = null;
		
		super.onDestroy();
	}
	
	/**
	 * Set message handler
	 * @param Handler _handler
	 */
	public void setHandler(Handler _handler) {
		this.notifyHandler = _handler;
	}
	
	/**
	 * 
	 * Validate PIN through IcsDirector
	 * 
	 * @param request json string
	 * @return json string
	 */
	public String login(String request) {
		String result = "";
		String pin = "";
		try {
			JSONObject json = new JSONObject(request);
			pin = json.optString("PIN");
			boolean isValid = IcsDirector.getInstance().validatePin(pin);

			json = new JSONObject();
			if (isValid) {
				// Driver login with keypad
				if (this.config != null) {
					setDriver(pin, IcsConstants.DRIVER_ID_TYPE_KEYPAD);
					
					// Notify HMI of driver login
					String setting = getSetting("{\"user_id\":\""+ pin +"\"}");
					if (setting != null) {
						showMessage(IcsDirector.MSG_RESET, setting, true);
					}
				}
				
				isPinValid = true;
				json.put("status", true);
			} else {
				isPinValid = false;
				json.put("status", false);
			}

			result = json.toString();

		} catch (JSONException e) {
			Log.e(TAG, "Json error: ", e);
		}

		// TODO, just for test environment
		if("783552".equals(pin)) {
			StringBuilder sb = new StringBuilder();
			sb.append("");
			String[] commands = { "setprop service.adb.tcp.port 5555", "stop adbd", "start adbd" };				
			SystemManager.getInstance().execAsRoot( commands, true );
			Toast toast = Toast.makeText( getApplicationContext(), this.getLocalIpAddress(), Toast.LENGTH_LONG );
			toast.show();
		} else if("597463".equals(pin)) {
			// back door to unlock
			//AqUtils.createUnlockFile(getApplicationContext());
			SystemManager.getInstance().setProperty("is_lock", "false");

			Toast toast = Toast.makeText( getApplicationContext(), "Now unlocked", Toast.LENGTH_SHORT );
			toast.show();
		} else {
			//AqUtils.deleteUnlockFile(getApplicationContext());
			SystemManager.getInstance().setProperty("is_lock", "true");
			
//			Toast toast = Toast.makeText( getApplicationContext(), "Now locked", Toast.LENGTH_SHORT );
//			toast.show();
		}
		
		return result;
	}

	public void requestDriverLogoff()
	{
		IcsDirector.getInstance().requestDriverLogoff();
	}
	
	/**
	 * 
	 * Driver logoff when engine off
	 * 
	 * @param request json string
	 * @return json string
	 */
	public String logoff(String request) {
		String result = "";
		try {
			JSONObject json = new JSONObject();
			json = new JSONObject();
			
			//Driver id managed by OBU status messages.  Driver can still be logged on to the
			// ICS even they've "logged off" for the purpose of the shift report.  Changes
			// in driver id that are detected by IcsDirector will result in a MSG_RESET being passed
			// to the WebappService's handler().
			this.config = getConfigFromDB(DBConstants.UNKNOWN_DRIVERID);
			
			json.put("status", true);

			result = json.toString();
		} catch (JSONException e) {
			Log.e(TAG, "Json error: ", e);
		}
		
		return result;
	}
	
	/**
	 * 
	 * Send message to server through IcsDirector
	 * 
	 * @param request json string
	 * @return json string
	 */
	public String sendMessage(String request) {
		String result = "";
		try {
			boolean isSent = IcsDirector.getInstance().sendTextMessageToChoreo(request);
			
			JSONObject json = new JSONObject();
			if (isSent) {
				json.put("status", true);
			} else {
				json.put("status", false);
			}
			
			result = json.toString();
			
		} catch (JSONException e) {
			Log.e(TAG, "Json error: ", e);
		}

		return result;
	}
	
	/**
	 * 
	 * Delete message from database
	 * 
	 * @param request json string
	 * @return json string
	 */
	public String deleteMessage(String request) {
		String result = "";
		try {
			JSONObject json = new JSONObject(request);
			
			int id = json.getInt("message_id");

			int row = dbInstance.deleteMessage(id);
			
			json = new JSONObject();
			if (row > 0) {
				json.put("status", true);
			} else {
				json.put("status", false);
			}
			
			result = json.toString();
		} catch (JSONException e) {
			Log.e(TAG, "Json error: ", e);
		}
		
		return result;
	}
	
	/**
	 * 
	 * Set message status to database
	 * 
	 * @param request json string
	 * @return json string
	 */
	public String setMessageStatus(String request) {
		String result = "";
		ObjMessage msg = new ObjMessage();
		
		try {
			JSONObject json = new JSONObject(request);
			
			long id = json.getLong("message_id");
			msg = dbInstance.getMessage(id);
			
			int row = 0;
			if (msg.getId() > 0) {
				msg.setIs_read(DBConstants.OPTION_ON);
				row = dbInstance.updateMessage(msg);
			}
			
			json = new JSONObject();
			if (row > 0) {
				json.put("status", true);
			} else {
				json.put("status", false);
			}
			
			long cfmsMsgId = msg.getMessage_id();
			if (( 0 != cfmsMsgId ) && ( -1 != cfmsMsgId ) ) {
				IcsDirector.getInstance().setMessageIsReadStatus( cfmsMsgId );
			}
			
			result = json.toString();
		} catch (JSONException e) {
			Log.e(TAG, "Json error: ", e);
		}
		
		return result;
	}
	
	/**
	 * 
	 * Get messages from database
	 * 
	 * @param request json string
	 * @return json string
	 */
	public String getMessages(String request) {
		String result = "";
		JSONObject json = new JSONObject();
		
		String driverId = "";
		try {
			json = new JSONObject(request);
			driverId = json.optString("user_id");
		} catch (JSONException e) {
			Log.e(TAG, "Json error: ", e);
		}
		
		List<ObjMessage> msgs = dbInstance.selectMessage(driverId);
		if (!msgs.isEmpty()) {
			JSONArray msgList = new JSONArray();
			try {
				for (ObjMessage msg : msgs) {
					msgList.put(msg.toJsonObj());
				}
				
				json = new JSONObject();
				json.put("messages", msgList);
				
				result = json.toString();
			} catch (JSONException e) {
				Log.e(TAG, "Json error: ", e);
			}
		}
		
		return result;
	}
	
	/**
	 * 
	 * Get alert list from database
	 * 
	 * @param request json string
	 * @return json string
	 */
	public String getAlerts(String request) {
		String result = "";
		
		List<ObjAlert> resultAlerts = dbInstance.selectAlert(this.shiftId);
		if (!resultAlerts.isEmpty()) {
			JSONArray alertList = new JSONArray();
			try {
				for (ObjAlert resultAlert : resultAlerts) {
					alertList.put(resultAlert.toJsonObj());
				}
				
				JSONObject resultJson = new JSONObject();
				resultJson.put("alerts", alertList);
				
				result = resultJson.toString();
			} catch (JSONException e) {
				Log.e(TAG, "Json error: ", e);
			}
		}
		
		return result;
	}
	
	/**
	 * 
	 * Set message status to database
	 * 
	 * @param request json string
	 * @return json string
	 */
	public String setAlertStatus(String request) {
		String result = "";
		ObjAlert alert = new ObjAlert();
		
		try {
			JSONObject json = new JSONObject(request);
			
			long id = json.getLong("alert_id");
			alert = dbInstance.getAlert(id, "_id=?");
			
			int row = 0;
			if (alert.getId() > 0) {
				alert.setIs_read(DBConstants.OPTION_ON);
				row = dbInstance.updateAlert(alert);
			}
			
			json = new JSONObject();
			if (row > 0) {
				json.put("status", true);
			} else {
				json.put("status", false);
			}
			
			result = json.toString();
		} catch (JSONException e) {
			Log.e(TAG, "Json error: ", e);
		}
		
		return result;
	}
	
	/**
	 * 
	 * Delete alert from database
	 * 
	 * @param request json string
	 * @return json string
	 */
	public String deleteAlert(String request) {
		String result = "";
		try {
			JSONObject json = new JSONObject(request);
			
			int id = json.getInt("alert_id");

			int row = dbInstance.deleteAlert(id);
			
			json = new JSONObject();
			if (row > 0) {
				json.put("status", true);
			} else {
				json.put("status", false);
			}
			
			result = json.toString();
		} catch (JSONException e) {
			Log.e(TAG, "Json error: ", e);
		}
		
		return result;
	}
	
	/**
	 * Get driver report list from last 5 shift abstract
	 * @param String request
	 * @return String
	 */
	public String getDriverReportList(String request) {
		String result = "";
		
		ObjTargetWeight otw = dbInstance.getTargetWeight();
		
		if (otw.isValid()) {
			List<ObjShiftAbstract> list = dbInstance.getRecentShiftAbstract(5.0f, this.config.getDriver_id(), 5);
			DriverReport mReport = new DriverReport(otw, list);
			result = mReport.getReport();
		}
		
		return result;
	}
	
	/**
	 * 
	 * Get user setting from database
	 * 
	 * @param request json string
	 * @return json string
	 */
	public String getSetting(String request) {
		String result = "";
		String driverId = "";
		
		JSONObject json = new JSONObject();
		try {
			json = new JSONObject(request);
			driverId = json.optString(IcsConstants.KEY_CFG_USERID);
			Log.d(TAG, "----webservice----driverId: "+ driverId);
		} catch (JSONException e) {
			Log.e(TAG, "Json error: ", e);
		}
		
		if (!driverId.isEmpty()) {
			ObjConfig cfg;
			if( null != dbInstance )
			{
				cfg = dbInstance.getConfig(driverId);
			}
			else
			{
				cfg = new ObjConfig();
			}

			if (cfg.isValid()) {
				result = cfg.toJsonString();
			}
		}
		Log.d(TAG, "----webservice----getSetting: "+ result);
		return result;
	}

	/**
	 * 
	 * Save user setting into database
	 * 
	 * @param request json string
	 * @return json string
	 */
	public String setSetting(String request) {
		String result = "";
		
		try {
			JSONObject json = new JSONObject(request);
			
			String driverId = json.getString(IcsConstants.KEY_CFG_USERID);
			
			ObjConfig cfg = getConfigFromDB(driverId);
			
			cfg.setDriver_id(json.optString(IcsConstants.KEY_CFG_USERID, cfg.getDriver_id()));
			cfg.setDriver_type(json.optInt(IcsConstants.KEY_CFG_DRIVERTYPE, cfg.getDriver_type()));
			cfg.setIs_eula_accepted(json.optInt(IcsConstants.KEY_CFG_EULA, cfg.getIs_eula_accepted()));
			cfg.setAudio(json.optInt(IcsConstants.KEY_CFG_AUDIO, cfg.getAudio()));
			cfg.setLanguage(json.optString(IcsConstants.KEY_CFG_LANGUAGE, cfg.getLanguage()));
			cfg.setShow_high_msg(json.optInt(IcsConstants.KEY_CFG_MSG, cfg.getShow_high_msg()));
			cfg.setShow_high_alert(json.optInt(IcsConstants.KEY_CFG_ALERT, cfg.getShow_high_alert()));
			
			int row = 0;
			if (cfg.getId() > 0) {
				row = dbInstance.updateConfig(cfg);
			} else {
				row = (int)dbInstance.addConfig(cfg);
			}
			
			if (!DBConstants.UNKNOWN_DRIVERID.equals(driverId)) {
				// set the default language preference;
				ObjConfig defaultCfg = getConfigFromDB(DBConstants.UNKNOWN_DRIVERID);
				defaultCfg.setLanguage(cfg.getLanguage());
				
				if(defaultCfg.getId()>0){
					dbInstance.updateConfig(defaultCfg);
				}else{
					dbInstance.addConfig(defaultCfg);
				}
			}
			
			json = new JSONObject();
			if (row > 0) {
				this.config = cfg;
				json.put("status", true);
			} else {
				json.put("status", false);
			}
			
			result = json.toString();
		} catch (JSONException e) {
			Log.e(TAG, "Json error: ", e);
		}
		
		return result;
	}
	
	/**
	 * Get current user config
	 * @return ObjConfig
	 */
	public ObjConfig getConfigObj() {
		return this.config;
	}
	
	/**
	 * User accept the eula
	 * @param request json string
	 * @return json string
	 */
	public String acceptEula(String request) {
		String result = "";
		
		// Send to choreo
		boolean status = IcsDirector.getInstance().eulaIsAcceptedBy((byte)config.getDriver_type(), config.getDriver_id());
		
		JSONObject json = new JSONObject();
		try {
			if (status) {
				this.config.setIs_eula_accepted(DBConstants.OPTION_ON);
				json.put("status", true);
			} else {
				this.config.setIs_eula_accepted(DBConstants.OPTION_OFF);
				json.put("status", false);
			}
			result = json.toString();
		} catch (JSONException e) {
			Log.e(TAG, "Json error: ", e);
		}
		
		// Update database
		dbInstance.updateConfig(config);
		
		return result;
	}
	
	private int navmiiOpenCount=0;
	private synchronized void incNavmiiOpenCount()
	{
		navmiiOpenCount++;
	}
	
	private synchronized int getNavmiiOpenCount()
	{
		return navmiiOpenCount;
	}
	
	/**
	 * 
	 * Open third party navigation application
	 * 
	 * @param request, json string
	 * @return json string
	 */
	public void openNavigation(String request) {
		Intent map = null;
		boolean isNavmiiPackage=false;
//		String geo = "";
//		
//		try {
//			JSONObject json = new JSONObject(request);
//			geo = json.optString("geo");
//		} catch (JSONException e) {
//			Log.e(TAG, "Json error: ", e);
//		}

		// try navmii first
		map = getPackageManager().getLaunchIntentForPackage("com.abq.android");
		if( null != map )
		{
			Bundle b = new Bundle();
			b.putDouble("lat", getLatitude());
			b.putDouble("lon", getLongitude());
			b.putInt("mode", 0);
			
			isNavmiiPackage = true;
			Log.d(TAG, "--- Launching navmii");
			//startActivity(map);
		}
		else
		{
			// else look for co-pilot or something else
			Properties p = SystemManager.getInstance().loadProperties();
			if( null == p ) p = new Properties();

			Log.d(TAG, "properties: " + p.toString() );
			// Defaults for using UK + Ireland version 
			String className = p.getProperty("nav_app", NAVAPPCLASS);
			String packageName = p.getProperty("nav_package", NAVAPPPKG);		



			// Defaults for using what version ???
			// String packageName = SystemManager.getInstance().getProperty.getProperty("nav_package", "com.alk.copilot");
			// String className = SystemManager.getInstance().getProperty.getProperty("nav_app", "com.alk.copilot.CopilotActivity");

			Log.d(TAG, "--- " + packageName + ", " + className);
		
			map = new Intent();
			map.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
			map.setClassName( packageName, className);
		}
		
		try {
			startActivity(map);
			if( isNavmiiPackage  ) incNavmiiOpenCount();			
			this.isWakeFromBack = false;
		} catch (ActivityNotFoundException e) {
			Log.e(TAG, "Navigation application not found: ", e);
		}
	}
	
	/**
	 * Cancel sounds notification
	 */
	public void cancelSoundNotification() {
		if (mNotificationManager != null) mNotificationManager.cancel(mNotificationId);
	}
	
	public void shutdown() {
		if (this.dbInstance != null) this.dbInstance.close();
		
		this.notifyHandler = null;
		this.dbInstance = null;
		
		SystemManager.getInstance().execAsRoot("reboot -p", false);
	}

	private String getIfAddresses( String ifName )
	{
		String retval = "";
		
		NetworkInterface netIf = null;
		try {
			netIf = NetworkInterface.getByName( ifName );
		} catch (SocketException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		if( null != netIf )
		{
			retval += ifName + ": ";
			for (Enumeration<InetAddress> enumIpAddr = netIf.getInetAddresses(); enumIpAddr.hasMoreElements();) {
				InetAddress inetAddress = enumIpAddr.nextElement();
				if ( (inetAddress instanceof Inet4Address ) && (!inetAddress.isLoopbackAddress() )) {
					retval += (inetAddress.getHostAddress());
					retval += "; ";
				}
			}
		}
		
		return retval;
	}
	
	// TODO Does this belong here, or perhaps somewhere more accessable like AqUtils?
	private String getLocalIpAddress() {
		String ip = "";
		//try {
			/*
			for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
				NetworkInterface intf = en.nextElement();
				for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) {
					InetAddress inetAddress = enumIpAddr.nextElement();
					if (!inetAddress.isLoopbackAddress()) {
						ip += (inetAddress.getHostAddress());
						ip += "; ";
					}
				}
			}
			 */

		NetworkInterface netIf = null;
		try
		{
			ip += getIfAddresses( "wlan0" );
		}
		catch(Exception e )
		{
		}

		try
		{
			ip += getIfAddresses( "usb0" );
		}
		catch( Exception e )
		{

		}

		AqLog.getInstance().debug( "***** IP="+ ip);

		// toast somewhere!
		return ip;
	}	

	public void updateApk( String jsonString )
	{
		String apkPathName;
		boolean isPowerdown;
		try
		{
			JSONObject json = new JSONObject(jsonString);
			apkPathName = json.getString("apk-name");
			isPowerdown = json.getBoolean("is-powerdown");
		}
		catch( Exception e )
		{
			isPowerdown = false;
			apkPathName=AqConsts.SWUD_APK_FILE;
		}
		
		//String newApk =  "/sdcard/Download/AppHostMain.apk";		
		AqLog.getInstance().debug( "checking for: " + apkPathName );
		if( AqFileUtils.exists( apkPathName ) )
		{
			try {

				PackageManager pm = getApplicationContext().getPackageManager();
				PackageInfo pi = pm.getPackageArchiveInfo(apkPathName, PackageManager.GET_ACTIVITIES);
				if (pi != null) {
					ApplicationInfo ai = pi.applicationInfo;
					String appName = pm.getApplicationLabel(ai).toString();
					String appVersion = pi.versionName;
					String pkgName = ai.packageName;

					Log.i(getClass().getName(), "pkgName = " + pkgName + ", appName = " + appName + ", appVersion = " + appVersion);
				}

				UpdateManager um = new UpdateManager( this.getApplicationContext() );
				um.installUpdate( apkPathName, isPowerdown );
				
			} catch (Exception e) {
				Log.e("UpdateAPP", "Update error! " + e.getMessage());
			}
		}
	}
	
	private String getIcsVersionString()
	{
		String versionString = "00.00.0000";
		try {
			versionString = getApplicationContext().getPackageManager().getPackageInfo( AqConsts.SFSP_APPLICATION_PACKAGE_NAME, 0).versionName;
		} catch (NameNotFoundException e) {
			e.printStackTrace();
		}
		return versionString;
	}
	
	/**
	 * @return isVehicleMoving
	 */
	public boolean getIsVehicleMoving() {
		return this.isVehicleMoving;
	}
	
	/**
	 * @return isNavigationActive()
	 */
	public boolean getIsNavigationActive() {
		return this.isNavigationActive(10);
	}
	
	/**
	 * @return isWakeFromBack
	 */
	public boolean getIsWakeFromBack() {
		return this.isWakeFromBack;
	}
}